Conversation
…rehensive data handling and performance metrics - Added GET endpoint for agent analytics to fetch appointment statistics and quick stats. - Introduced GET endpoint for agent trends analysis, including traffic, response time, and service type trends. - Created GET endpoint for agent dashboard to provide real-time statistics on appointments and submissions. - Developed SimplifiedAnalytics component to display analytics data, performance metrics, and trends in a user-friendly interface. - Enhanced data fetching logic with error handling and loading states for better user experience.
… improved functionality
…ved data handling and type definitions
WalkthroughIntroduces multiple agent analytics API routes (analytics, performance, trends, reports) and wires UI components to fetch real data. Adds agent dashboard API and connects StatsOverview. Adds auth helpers for agent/admin. Minor adjustments to admin management, debug route signatures, and user pages. Replaces AnalyticsPage content with SimplifiedAnalytics. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Agent as Agent (Browser)
participant Simplified as SimplifiedAnalytics (UI)
participant API1 as /api/agent/analytics
participant API2 as /api/agent/analytics/performance
participant API3 as /api/agent/analytics/trends
participant Auth as verifyAgentAuth
participant DB as Database
Agent->>Simplified: Load page / set timeRange
Simplified->>API1: GET ?timeRange=...
Simplified->>API2: GET ?timeRange=...&view=personal
Simplified->>API3: GET ?timeRange=...
API1->>Auth: Verify
API2->>Auth: Verify
API3->>Auth: Verify
Auth-->>API1: isValid, agent
Auth-->>API2: isValid, agent
Auth-->>API3: isValid, agent
API1->>DB: Query appointments
API2->>DB: Query appointments/agents
API3->>DB: Query appointments
DB-->>API1: Results
DB-->>API2: Results
DB-->>API3: Results
API1-->>Simplified: quickStats, chartData, totals
API2-->>Simplified: personalMetrics, chartData, skills/team
API3-->>Simplified: trends, peak hours, resources, queries
Simplified-->>Agent: Render dashboard
sequenceDiagram
autonumber
actor Agent as Agent (Browser)
participant Reports as /api/agent/analytics/reports
participant Auth as verifyAgentAuth
participant DB as Database
Agent->>Reports: GET ?action=getHistory
Reports->>Auth: Verify
Auth-->>Reports: isValid, agent
Reports->>DB: Fetch recent appointments (agent)
DB-->>Reports: Records
Reports-->>Agent: recentReports
Agent->>Reports: POST { action: generateReport, reportConfig }
Reports->>Auth: Verify
Auth-->>Reports: isValid, agent
Reports->>DB: Fetch appointments in range
DB-->>Reports: Records
Reports-->>Agent: reportId, reportData, downloadUrl
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Pull Request Overview
This PR integrates agent analytics functionality with the backend by replacing mock data with real API calls. The main purpose is to connect frontend analytics components with actual server-side data endpoints and introduce new simplified analytics dashboards.
- Replace static mock data in agent analytics components with dynamic API calls
- Add new analytics API routes for dashboard stats, trends, performance metrics, and report generation
- Introduce a new SimplifiedAnalytics component for a more streamlined analytics view
- Clean up unused code and improve authentication middleware
Reviewed Changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib/auth/agent-middleware.ts |
Adds simplified agent verification function for API routes |
src/lib/auth/admin-middleware.ts |
Adds admin authentication verification function for API consistency |
src/components/agent/dashboard/StatsOverview.tsx |
Replaces mock stats with real API data from /api/agent/dashboard |
src/components/agent/analytics/SystemTrends.tsx |
Integrates with trends API and replaces hardcoded data |
src/components/agent/analytics/SimplifiedAnalytics.tsx |
New component providing simplified analytics dashboard with API integration |
src/components/agent/analytics/ReportGenerator.tsx |
Connects to reports API for generation and history |
src/components/agent/analytics/PerformanceMetrics.tsx |
Replaces mock performance data with API calls |
src/components/agent/analytics/AnalyticsDashboard.tsx |
Updates main analytics dashboard with real data |
src/app/api/agent/dashboard/route.ts |
New API endpoint for agent dashboard statistics |
src/app/api/agent/analytics/trends/route.ts |
New API endpoint for system trends and analysis |
src/app/api/agent/analytics/route.ts |
New API endpoint for core analytics data |
src/app/api/agent/analytics/reports/route.ts |
New API endpoint for report generation and management |
src/app/api/agent/analytics/performance/route.ts |
New API endpoint for performance metrics |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| // Form Components | ||
| const PasswordStrengthIndicator = ({ password, language = 'en' }: { password: string; language?: Language }) => { | ||
| const PasswordStrengthIndicator = ({ password }: { password: string; language?: Language }) => { |
There was a problem hiding this comment.
The language parameter is defined in the destructured props but not used in the component implementation, yet it's still present in the type definition. Either remove it from the type or use it in the getStrength function.
| const PasswordStrengthIndicator = ({ password }: { password: string; language?: Language }) => { | |
| const PasswordStrengthIndicator = ({ password }: { password: string }) => { |
| <div className="flex items-center justify-between mb-3"> | ||
| <h4 className="text-sm font-medium text-muted-foreground group-hover:text-foreground transition-colors duration-300"> | ||
| {t[key]} | ||
| {t[key as keyof MetricsTranslation]} |
There was a problem hiding this comment.
The type assertion 'as keyof MetricsTranslation' is unsafe because 'key' comes from Object.keys(personalMetrics) which may not match the keys in MetricsTranslation. This could cause runtime errors if the keys don't align.
| <span>•</span> | ||
| <span>{report.size}</span> | ||
| <span>•</span> | ||
| <span>{formatDate(report.generatedAt)}</span> |
There was a problem hiding this comment.
The property name has changed from 'generatedDate' to 'generatedAt' in the Report interface, but this change may not be consistent throughout the codebase. Verify that all backend APIs return 'generatedAt' instead of 'generatedDate'.
| <span>{formatDate(report.generatedAt)}</span> | |
| <span>{formatDate(report.generatedAt ?? report.generatedDate)}</span> |
| import bcrypt from "bcryptjs"; | ||
|
|
||
| export async function POST(request: NextRequest) { | ||
| export async function POST() { |
There was a problem hiding this comment.
The request parameter was removed but this endpoint should validate the request or require authentication to prevent unauthorized admin creation. Consider adding proper authentication or request validation.
| import Admin from "@/lib/models/adminSchema"; | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| export async function GET() { |
There was a problem hiding this comment.
This debug endpoint exposes admin information without any authentication. This could be a security risk in production environments.
There was a problem hiding this comment.
Actionable comments posted: 9
🔭 Outside diff range comments (6)
src/app/api/debug/setup-admins/route.ts (2)
6-12: Lock down this debug endpoint in non-development environmentsThis route seeds accounts and exposes sensitive data. It must be disabled outside development to prevent accidental production execution.
Apply this diff to guard the endpoint:
export async function POST() { try { + // Hard stop for non-development environments + if (process.env.NODE_ENV !== "development") { + return NextResponse.json( + { success: false, message: "setup-admins is disabled outside development" }, + { status: 403 } + ); + } + await connectDB();
97-118: Do not return plaintext credentials in API responsesReturning seeded admin credentials is a serious security/PII issue, even for “debug” routes. If you need the credentials for local dev, conditionally include them only in development; otherwise omit entirely.
Apply this diff to conditionally include credentials only in development:
- return NextResponse.json({ - success: true, - message: "Admin setup completed successfully", - admins: allAdmins.map((admin) => ({ - id: admin._id, - fullName: admin.fullName, - email: admin.email, - role: admin.role, - accountStatus: admin.accountStatus, - createdAt: admin.createdAt, - })), - credentials: { - superAdmin: { - email: "superadmin@govlink.lk", - password: "ThisIsInsane", - }, - regularAdmin: { - email: "admin@govlink.lk", - password: "TestAdmin123", - }, - }, - }); + const payload: any = { + success: true, + message: "Admin setup completed successfully", + admins: allAdmins.map((admin) => ({ + id: admin._id.toString(), + fullName: admin.fullName, + email: admin.email, + role: admin.role, + accountStatus: admin.accountStatus, + createdAt: admin.createdAt, + })), + }; + if (process.env.NODE_ENV === "development") { + payload.credentials = { + superAdmin: { email: "superadmin@govlink.lk", password: "ThisIsInsane" }, + regularAdmin: { email: "admin@govlink.lk", password: "TestAdmin123" }, + }; + } + return NextResponse.json(payload);src/components/agent/analytics/SystemTrends.tsx (4)
297-301: Guard TrendChart against empty datasets to avoid -Infinity/NaN heightsWhen data is empty, Math.max(...[]) yields -Infinity and height math can be undefined. Add an early return and a safe max baseline.
Apply this diff:
- const TrendChart = ({ data, metric, color }: { data: TrendDataPoint[], metric: keyof TrendDataPoint, color: string }) => { - const maxValue = Math.max(...data.map(d => d[metric] as number)); + const TrendChart = ({ data, metric, color }: { data: TrendDataPoint[], metric: keyof TrendDataPoint, color: string }) => { + if (!data || data.length === 0) { + return <div className="h-40 flex items-center justify-center text-sm text-muted-foreground">No data</div>; + } + const maxValue = Math.max(1, ...data.map(d => d[metric] as number));
400-413: Prevent -Infinity/NaN in Peak/Avg/Low traffic when trendsData is emptyRendering with empty trendsData shows -Infinity/Infinity/NaN. Guard calculations.
Apply this diff:
- <div className="font-bold text-[#FFC72C]">{Math.max(...trendsData.map(d => d.traffic))}</div> + <div className="font-bold text-[#FFC72C]">{trendsData.length ? Math.max(...trendsData.map(d => d.traffic)) : 0}</div> ... - {Math.round(trendsData.reduce((acc, d) => acc + d.traffic, 0) / trendsData.length)} + {trendsData.length ? Math.round(trendsData.reduce((acc, d) => acc + d.traffic, 0) / trendsData.length) : 0} ... - <div className="font-bold text-[#008060]">{Math.min(...trendsData.map(d => d.traffic))}</div> + <div className="font-bold text-[#008060]">{trendsData.length ? Math.min(...trendsData.map(d => d.traffic)) : 0}</div>
423-437: Prevent -Infinity/NaN in Response Time stats when trendsData is emptySame issue for responseTime max/avg/min. Guard the math.
Apply this diff:
- {Math.max(...trendsData.map(d => d.responseTime)).toFixed(1)}s + {(trendsData.length ? Math.max(...trendsData.map(d => d.responseTime)) : 0).toFixed(1)}s ... - {(trendsData.reduce((acc, d) => acc + d.responseTime, 0) / trendsData.length).toFixed(1)}s + {(trendsData.length ? (trendsData.reduce((acc, d) => acc + d.responseTime, 0) / trendsData.length) : 0).toFixed(1)}s ... - {Math.min(...trendsData.map(d => d.responseTime)).toFixed(1)}s + {(trendsData.length ? Math.min(...trendsData.map(d => d.responseTime)) : 0).toFixed(1)}s
447-447: Avoid mutating state-derived arrays during rendersort() mutates the original array (systemData.peakHoursData). Clone before sorting to prevent accidental state mutation in render.
Apply this diff:
- {peakHoursData.sort((a, b) => b.traffic - a.traffic).map((hour) => ( + {[...peakHoursData].sort((a, b) => b.traffic - a.traffic).map((hour) => (
🧹 Nitpick comments (36)
src/app/api/debug/setup-admins/route.ts (1)
100-107: Serialize ObjectId to string to avoid JSON serialization quirksMongoose ObjectId should be stringified for consistent API responses.
Apply this diff:
- id: admin._id, + id: admin._id.toString(),src/app/user/auth/register/page.tsx (1)
199-231: Either localize the strength labels or drop the unused language propRight now, PasswordStrengthIndicator accepts a language prop in its type but ignores it at runtime, while callers still pass it. Either remove the prop end-to-end or use it to localize labels.
Apply this diff to localize labels and keep the API clear:
-const PasswordStrengthIndicator = ({ password }: { password: string; language?: Language }) => { +const PasswordStrengthIndicator = ({ password, language = 'en' }: { password: string; language?: Language }) => { + const strengthLabels: Record<Language, { weak: string; fair: string; strong: string }> = { + en: { weak: 'Weak', fair: 'Fair', strong: 'Strong' }, + si: { weak: 'දුර්වල', fair: 'සාමාන්ය', strong: 'දृඝ' }, + ta: { weak: 'பலவீனமானது', fair: 'சராசரி', strong: 'வலிமையானது' }, + }; + const getStrength = () => { if (password.length === 0) return { score: 0, label: '', color: '' }; - if (password.length < 6) return { score: 1, label: 'Weak', color: '#FF5722' }; - if (password.length < 10) return { score: 2, label: 'Fair', color: '#FFC72C' }; - return { score: 3, label: 'Strong', color: '#008060' }; + if (password.length < 6) return { score: 1, label: strengthLabels[language].weak, color: '#FF5722' }; + if (password.length < 10) return { score: 2, label: strengthLabels[language].fair, color: '#FFC72C' }; + return { score: 3, label: strengthLabels[language].strong, color: '#008060' }; };src/app/api/debug/check-admins/route.ts (2)
14-14: Avoid logging full admin documentsLogging the entire admin array leaks PII into logs. Log the count instead.
Apply this diff:
- console.log("Found admins:", admins); + console.log("Found admins:", admins.length);
20-21: Stringify ObjectId in API responseEnsure id is a string for client consumption and consistent typing.
Apply this diff:
- id: admin._id, + id: admin._id.toString(),src/lib/auth/admin-middleware.ts (1)
192-214: Avoid redundant DB connect and select safe fields; handle deleted adminauthenticateAdmin already connects to the DB. Also, selecting only safe fields reduces accidental exposure downstream and handling a not-found edge makes the helper more robust.
Apply this diff:
-export const verifyAdminAuth = async (request: NextRequest) => { +export const verifyAdminAuth = async (request: NextRequest) => { const authResult = await authenticateAdmin(request); if (!authResult.success) { return { isValid: false, admin: null, error: authResult.message }; } - // Fetch full admin data - await connectDB(); - const admin = await Admin.findById(authResult.admin!.userId); + // Fetch admin data (select safe fields) + const admin = await Admin.findById(authResult.admin!.userId) + .select("fullName email role accountStatus lastLoginAt createdAt updatedAt"); + if (!admin) { + return { + isValid: false, + admin: null, + error: "Admin not found" + }; + } return { isValid: true, admin, error: null }; };src/app/admin/admin-management/page.tsx (1)
85-104: Consider toggling loading state at the start of fetchAdmins (for refresh UX) and add abort supportCurrent implementation only sets loading to false in finally. If you ever reuse fetchAdmins to refresh the list after create/update/delete, you won’t show a loading indicator. Also, adding an AbortController prevents setState on unmounted components during navigation.
Apply this diff to show refresh loading and support abort:
- const fetchAdmins = useCallback(async () => { - try { + const fetchAdmins = useCallback(async () => { + const controller = new AbortController(); + try { + setLoading(true); const response = await fetch("/api/admin/admins", { method: "GET", - headers: getAuthHeaders(), + headers: getAuthHeaders(), credentials: "include", + signal: controller.signal, }); if (response.ok) { const data = await response.json(); setAdmins(data.admins); } else { console.error("Failed to fetch admins"); } } catch (error) { console.error("Error fetching admins:", error); } finally { setLoading(false); } - }, [getAuthHeaders]); + return () => controller.abort(); + }, [getAuthHeaders]);src/app/api/agent/analytics/performance/route.ts (2)
62-63: Clamp firstContactRate to 100 to avoid >100% values in UIWith resolutionRate near 100, 60 + 0.3*resolutionRate can exceed 100. Clamp to keep percentages valid.
- const firstContactRate = totalAppointments > 0 ? - (60 + (resolutionRate * 0.3)) : 0; + const firstContactRate = totalAppointments > 0 + ? Math.min(100, 60 + resolutionRate * 0.3) + : 0;
150-169: Avoid N+1 queries when building team rankingsThe per-agent Appointment.find call inside map causes N+1 DB roundtrips. Consider an aggregation to group by assignedAgent over the time window and compute counts in one query, then join to the agents list in memory.
src/lib/auth/agent-middleware.ts (1)
175-210: Prefer excluding id when spreading agent to avoid duplicate identifiersverifyAgentAuth currently spreads authResult.agent, which includes id, after adding _id. This creates both id and _id on the returned object. While generally harmless, eliminating id avoids confusion and accidental downstream reliance.
Apply this diff:
- return { - isValid: true, - agent: { - _id: authResult.agent.id, - ...authResult.agent - } - }; + const { id, ...agentRest } = authResult.agent; + return { + isValid: true, + agent: { + _id: id, + ...agentRest, + }, + };src/components/agent/dashboard/StatsOverview.tsx (3)
120-124: Remove console logs before shippingThese logs are useful during development but noisy in production. Consider gating with NODE_ENV or removing.
- const result = await response.json(); - console.log('Dashboard API Response:', result); - console.log('Dashboard Data:', result.data); - setDashboardData(result.data); + const result = await response.json(); + setDashboardData(result.data);
66-95: Unused confirmedAppointments in DashboardDataType includes confirmedAppointments but no card renders it. Either add a card or remove it from the type to reduce confusion.
Would you prefer we add a Confirmed Appointments card or drop the field from this component’s local type to match current UI?
106-137: Optional: abort in-flight fetches on unmount or refreshTo avoid setState on unmounted component edge cases, wrap fetchStats with AbortController and check signal.aborted before updating state.
I can provide a patch using AbortController if you want to harden fetchStats.
src/components/agent/analytics/SystemTrends.tsx (3)
346-358: selectedMetric is not wired to the chart; hook it up for expected behaviorButtons update selectedMetric but the primary TrendChart always renders traffic. Map selectedMetric to the appropriate metric field.
Apply this diff:
- <TrendChart data={trendsData} metric="traffic" color="#FFC72C" /> + <TrendChart + data={trendsData} + metric={ + selectedMetric === 'traffic' ? 'traffic' : + selectedMetric === 'performance' ? 'responseTime' : + selectedMetric === 'resources' ? 'cpuUsage' : + 'errorRate' + } + color="#FFC72C" + />Also applies to: 398-398
253-275: Cancel in-flight fetches on timeRange changes/unmount to avoid state updates on unmounted componentsUse AbortController and check
signal.abortedbefore setting state. This also avoids racing responses overwriting newer data.I can provide a patch adding AbortController and cleanup in useEffect if you’d like.
176-226: Minor i18n nit: Mixed-script characters in Tamil translationThere appears to be a Devanagari segment in a Tamil string (line 207). Please review and correct the mixed script.
src/components/agent/analytics/AnalyticsDashboard.tsx (1)
123-146: Set loading on re-fetch to avoid showing stale data during timeRange changesCurrently only isRefreshing is toggled; the page-level spinner never shows after the initial load. Optionally set loading true before fetch to present a consistent UX on timeRange changes.
Apply this diff:
- try { - setIsRefreshing(true); + try { + setIsRefreshing(true); + setLoading(true);src/app/api/agent/dashboard/route.ts (2)
64-70: Unify trend payload shape for consistent consumersUsing a literal "Scheduled" for trend.value breaks consistency with percentage changes used elsewhere. Consider standardizing trend.value to a delta-like string (e.g., "+0%") and adding a separate status if needed.
Please confirm how StatsOverview (or other consumers) expect trend for confirmedAppointments. If they expect a percent-like value, I can provide a patch to align this field.
13-14: Optional: Skip DB connection when returning mock dataconnectDB is unnecessary here until real queries are added, and skipping it reduces overhead.
src/components/agent/analytics/PerformanceMetrics.tsx (2)
174-196: Cancel in-flight requests to prevent race conditions and state updates on unmountedIntroduce AbortController and clean up in useEffect. This avoids stale responses overriding newer state.
I can supply a patch adding AbortController wiring if you want to adopt it here and in sibling analytics components for consistency.
284-285: Safer fallback for missing translation keysIf the backend adds a metric without a translation key, this will render undefined. Provide a fallback to the key string.
Apply this diff:
- {t[key as keyof MetricsTranslation]} + {t[key as keyof MetricsTranslation] ?? String(key)}src/app/api/agent/analytics/route.ts (1)
42-46: Reduce query payload for performanceOnly a few fields are used (status, createdAt, serviceType). Use select(...).lean() to cut transfer and de-hydration overhead.
Example:
const agentAppointments = await Appointment.find( { assignedAgent: agentId, createdAt: { $gte: startDate, $lte: now } }, 'status createdAt serviceType' ).lean();Also consider adding a compound index on { assignedAgent: 1, createdAt: 1 } in the Appointment schema to optimize these range queries.
src/app/api/agent/analytics/trends/route.ts (5)
42-45: Select only needed fields to reduce memory and serialization costYou only use createdAt, serviceType, and status downstream. Limit the projection to reduce payload and speed up serialization.
- const appointments = await Appointment.find({ - assignedAgent: agentId, - createdAt: { $gte: startDate, $lte: now } - }); + const appointments = await Appointment.find({ + assignedAgent: agentId, + createdAt: { $gte: startDate, $lte: now } + }).select('createdAt serviceType status');
131-131: More robust label formatting for service typesReplace only-first-underscore replace('_', ' ') with a global underscore replacement and proper title-casing.
- type: type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' '), + type: type + .replace(/_/g, ' ') + .replace(/\b\w/g, (c) => c.toUpperCase()),
86-106: Optional: Pre-binning for peak hour calculationCurrent approach is O(24 × N). For larger datasets, pre-binning into a 24-length counter array in a single pass will reduce complexity to O(N).
If you want, I can provide a refactor that builds a frequency map by hour in one pass and derives peak hours from it.
140-170: Clarify errorRate semanticserrorRate is a 0–2 value depending on loadFactor; consumers likely expect a percentage (0–100). Consider normalizing and documenting the unit.
41-45: Indexing advice for query performanceMake sure Appointment has a compound index on { assignedAgent: 1, createdAt: 1 } to keep these range queries fast as data grows.
src/app/api/agent/analytics/reports/route.ts (1)
125-147: Honor custom endDate and make endDate explicitIn custom period, endDate from the request is ignored. Also make endDate explicit for all periods to avoid relying on a closed-over now.
- const now = new Date(); - let startDate = new Date(); - - switch (reportConfig.period) { + const now = new Date(); + let startDate = new Date(); + let endDate = now; + const period = reportConfig?.period; + + switch (period) { case 'daily': startDate.setHours(0, 0, 0, 0); break; case 'weekly': startDate.setDate(now.getDate() - 7); break; case 'monthly': startDate.setMonth(now.getMonth() - 1); break; case 'quarterly': startDate.setMonth(now.getMonth() - 3); break; case 'custom': startDate = new Date(reportConfig.startDate); + if (reportConfig.endDate) { + endDate = new Date(reportConfig.endDate); + } break; default: startDate.setDate(now.getDate() - 7); }Note: The subsequent query/filter should use endDate instead of now (see previous comment).
src/components/agent/analytics/SimplifiedAnalytics.tsx (5)
205-216: Mislabelled stat: shows satisfactionRate under "Completion Rate"This card renders satisfactionRate but labels it "Completion Rate". Either switch to resolutionRate or rename the title to "Satisfaction Rate" for clarity.
- { - title: 'Completion Rate', - value: `${quickStats.satisfactionRate?.value || '0'}${quickStats.satisfactionRate?.unit || '%'}`, + { + title: 'Satisfaction Rate', + value: `${quickStats.satisfactionRate?.value || '0'}${quickStats.satisfactionRate?.unit || '%'}`, change: quickStats.satisfactionRate?.change || '0%', isPositive: quickStats.satisfactionRate?.isPositive || false,
253-254: Make the section header reflect the selected time rangeAvoid “Weekly” when the timeRange is today/month/quarter.
- <h3 className="text-lg font-semibold text-foreground">Weekly Performance</h3> + <h3 className="text-lg font-semibold text-foreground"> + {timeRange.charAt(0).toUpperCase() + timeRange.slice(1)} Performance + </h3>
447-448: Avoid rendering 'undefined' when unit is missingDefault the unit to an empty string.
- {metric.value}{metric.unit} + {metric.value}{metric.unit ?? ''}
93-94: Avoid noisy logs in productionWrap console logs with a NODE_ENV check so production consoles remain clean.
- console.log('Analytics data received:', result); + if (process.env.NODE_ENV !== 'production') { + console.log('Analytics data received:', result); + }- console.log('Performance data received:', result); + if (process.env.NODE_ENV !== 'production') { + console.log('Performance data received:', result); + }- console.log('Trends data received:', result); + if (process.env.NODE_ENV !== 'production') { + console.log('Trends data received:', result); + }Also applies to: 102-103, 111-112
72-90: Optional: Prevent race conditions on rapid timeRange changesAdd an AbortController to cancel in-flight requests when timeRange changes to avoid setting state from stale responses.
I can provide a small refactor using AbortController if you want to pursue this now.
Also applies to: 124-127
src/components/agent/analytics/ReportGenerator.tsx (4)
296-298: Remove duplicated commentThe “Fetch real report data from backend” comment appears twice consecutively.
- // Fetch real report data from backend - // Fetch real report data from backend + // Fetch real report data from backend
523-528: Hook "Generate Now" buttons to actions for accessibilityRight now generation is only bound to the card onClick. Also attach onClick to the button so keyboard users can trigger it.
<button disabled={isGenerating} + onClick={(e) => { e.stopPropagation(); handleQuickReport('performance'); }} className="w-full px-4 py-2 bg-gradient-to-r from-[#FFC72C] to-[#FF5722] text-white rounded-lg hover:from-[#FF5722] hover:to-[#8D153A] transition-all duration-300 disabled:opacity-50 font-medium shadow-lg hover:shadow-xl" ><button disabled={isGenerating} + onClick={(e) => { e.stopPropagation(); handleQuickReport('service'); }} className="w-full px-4 py-2 bg-gradient-to-r from-[#008060] to-[#FFC72C] text-white rounded-lg hover:from-[#FFC72C] hover:to-[#FF5722] transition-all duration-300 disabled:opacity-50 font-medium shadow-lg hover:shadow-xl" ><button disabled={isGenerating} + onClick={(e) => { e.stopPropagation(); handleQuickReport('operational'); }} className="w-full px-4 py-2 bg-gradient-to-r from-[#FF5722] to-[#8D153A] text-white rounded-lg hover:from-[#8D153A] hover:to-[#FF5722] transition-all duration-300 disabled:opacity-50 font-medium shadow-lg hover:shadow-xl" >Also applies to: 578-583, 632-637
330-358: Differentiate success message for scheduled vs generated reportsCurrently you always show “Report generated successfully!” even when scheduling. Use the schedule selection to show t.reportScheduled for the scheduled flow.
I can provide a small state tweak (e.g., successMessage) and wire it into the success toast if you’d like.
373-381: Check and handle non-OK responses for quick reportsYou ignore non-OK responses; consider logging and surfacing an error toast to match the generate flow.
- if (response.ok) { + if (response.ok) { setShowSuccess(true); setTimeout(() => setShowSuccess(false), 3000); // Refresh report history await fetchReportData(); - } + } else { + console.error('Failed to generate quick report:', response.status, await response.text()); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
src/app/admin/admin-management/page.tsx(4 hunks)src/app/agent/analytics/page.tsx(2 hunks)src/app/api/agent/analytics/performance/route.ts(1 hunks)src/app/api/agent/analytics/reports/route.ts(1 hunks)src/app/api/agent/analytics/route.ts(1 hunks)src/app/api/agent/analytics/trends/route.ts(1 hunks)src/app/api/agent/dashboard/route.ts(1 hunks)src/app/api/debug/check-admins/route.ts(1 hunks)src/app/api/debug/setup-admins/route.ts(1 hunks)src/app/user/auth/register/page.tsx(1 hunks)src/app/user/booking/new/page.tsx(0 hunks)src/app/user/dashboard/page.tsx(1 hunks)src/components/agent/analytics/AnalyticsDashboard.tsx(6 hunks)src/components/agent/analytics/PerformanceMetrics.tsx(3 hunks)src/components/agent/analytics/ReportGenerator.tsx(8 hunks)src/components/agent/analytics/SimplifiedAnalytics.tsx(1 hunks)src/components/agent/analytics/SystemTrends.tsx(2 hunks)src/components/agent/dashboard/StatsOverview.tsx(5 hunks)src/lib/auth/admin-middleware.ts(1 hunks)src/lib/auth/agent-middleware.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/app/user/booking/new/page.tsx
🧰 Additional context used
🧬 Code Graph Analysis (12)
src/app/api/agent/analytics/performance/route.ts (3)
src/lib/auth/agent-middleware.ts (1)
verifyAgentAuth(178-210)src/lib/services/appointmentService.ts (1)
Appointment(5-20)src/lib/services/departmentApiService.ts (1)
Agent(46-72)
src/app/api/debug/check-admins/route.ts (3)
src/app/api/admin/admins/[id]/route.ts (1)
GET(19-77)src/app/api/admin/admins/route.ts (1)
GET(8-57)src/app/api/auth/admin/route.ts (1)
GET(78-131)
src/app/api/agent/dashboard/route.ts (5)
src/app/api/debug/check-admins/route.ts (1)
GET(5-39)src/app/api/agent/analytics/performance/route.ts (1)
GET(7-212)src/app/api/agent/analytics/route.ts (1)
GET(6-164)src/app/api/agent/analytics/trends/route.ts (1)
GET(6-228)src/lib/auth/agent-middleware.ts (1)
verifyAgentAuth(178-210)
src/app/api/agent/analytics/route.ts (5)
src/app/api/agent/analytics/reports/route.ts (1)
GET(6-102)src/app/api/agent/analytics/performance/route.ts (1)
GET(7-212)src/app/api/agent/analytics/trends/route.ts (1)
GET(6-228)src/lib/auth/agent-middleware.ts (1)
verifyAgentAuth(178-210)src/lib/services/appointmentService.ts (1)
Appointment(5-20)
src/app/api/agent/analytics/trends/route.ts (8)
src/app/api/debug/check-admins/route.ts (1)
GET(5-39)src/app/api/agent/analytics/reports/route.ts (1)
GET(6-102)src/app/api/agent/analytics/performance/route.ts (1)
GET(7-212)src/app/api/agent/analytics/route.ts (1)
GET(6-164)src/lib/auth/agent-middleware.ts (1)
verifyAgentAuth(178-210)src/lib/services/appointmentService.ts (1)
Appointment(5-20)scripts/migrate-departments.js (3)
h(302-302)a(256-256)type(373-373)scripts/check-database.js (1)
count(95-97)
src/app/api/debug/setup-admins/route.ts (3)
src/app/api/auth/admin/login/route.ts (1)
POST(7-64)src/app/api/admin/admins/route.ts (1)
POST(60-151)src/app/api/auth/admin/route.ts (1)
POST(10-75)
src/components/agent/dashboard/StatsOverview.tsx (1)
src/lib/services/departmentApiService.ts (1)
DashboardData(24-31)
src/lib/auth/agent-middleware.ts (1)
src/lib/auth/agent-auth.ts (1)
authenticateAgent(7-7)
src/lib/auth/admin-middleware.ts (1)
src/lib/auth/AdminAuthContext.tsx (1)
Admin(9-16)
src/app/api/agent/analytics/reports/route.ts (6)
src/app/api/agent/dashboard/route.ts (1)
GET(5-92)src/app/api/agent/analytics/performance/route.ts (1)
GET(7-212)src/app/api/agent/analytics/route.ts (1)
GET(6-164)src/app/api/agent/analytics/trends/route.ts (1)
GET(6-228)src/lib/auth/agent-middleware.ts (1)
verifyAgentAuth(178-210)src/lib/services/appointmentService.ts (1)
Appointment(5-20)
src/app/admin/admin-management/page.tsx (1)
src/lib/services/departmentApiService.ts (1)
getAuthHeaders(113-119)
src/components/agent/analytics/SimplifiedAnalytics.tsx (1)
src/lib/services/departmentApiService.ts (1)
AnalyticsData(97-103)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build-Test
🔇 Additional comments (10)
src/app/user/dashboard/page.tsx (2)
277-349: ServiceCard simplification looks goodRemoving the color prop from the ServiceCard API and inlining the style palette simplifies the component surface without loss of functionality.
277-349: No stalecolorprop on ServiceCard foundI searched all TSX files and confirmed there are no
<ServiceCard color=…>usages. The onlycolor=props live inTrendChartcalls (e.g. insrc/components/agent/analytics/SystemTrends.tsx), so you can consider the removal ofcolorfromServiceCardcomplete.src/app/admin/admin-management/page.tsx (2)
3-3: Importing useCallback is appropriate for stabilizing fetch function identityGood call adding useCallback to avoid effect churn and unnecessary re-renders when passing the fetch function as a dependency.
106-113: Effect dependency updated to include fetchAdmins — goodThis prevents stale closure issues and ensures the effect re-runs if the headers function changes.
src/app/agent/analytics/page.tsx (1)
5-5: Switching to SimplifiedAnalytics component is straightforward and consistentImport and usage look correct. Assuming SimplifiedAnalytics accepts a language prop, this change is safe.
Also applies to: 52-52
src/app/api/agent/analytics/performance/route.ts (1)
50-56: No schema mismatches found for Appointment.status, assignedAgent, or createdAt
APPOINTMENT_STATUS includes'completed', and the AppointmentSchema defines bothassignedAgentandcreatedAt(via explicit field andtimestamps: true). Filters againstapt.status === 'completed'will work as intended.src/components/agent/analytics/AnalyticsDashboard.tsx (1)
165-229: LGTM: Quick stats wiring to backend is clean and guardedThe mapping from analyticsData.quickStats to UI looks correct, with safe fallback to empty array.
src/components/agent/analytics/PerformanceMetrics.tsx (1)
218-224: LGTM: Loading guard prevents chart math on empty dataEarly return avoids Math.max on empty arrays; this prevents Infinity/NaN issues in charts.
src/app/api/agent/analytics/route.ts (1)
57-63: Resolution rate equals satisfaction rate — confirm intentBoth satisfactionRate and resolutionRate are derived from completed/total. If satisfaction should come from ratings, this may mislead consumers.
Would you like me to adjust resolutionRate/satisfactionRate computation (e.g., use a rating field if present, or distinguish “resolved” vs “completed”)?
src/app/api/agent/analytics/reports/route.ts (1)
20-57: LGTM: GET action handlers structure and error handlingAuth gate, action routing, DB connect, and structured 401/400/500 responses are consistent with other agent APIs.
| const teamAvgScore = teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length; | ||
|
|
||
| const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({ | ||
| period: i + 1, | ||
| responseTime: 3.2 + (Math.sin(i * 0.4) * 1.0), | ||
| satisfaction: Math.max(75, teamAvgScore + (Math.cos(i * 0.3) * 8)), | ||
| resolution: Math.max(70, teamAvgScore + (Math.sin(i * 0.5) * 6)), | ||
| tickets: Math.max(2, Math.round(15 + (Math.sin(i * 0.6) * 5))) | ||
| })); | ||
|
|
||
| return NextResponse.json({ | ||
| success: true, | ||
| data: { | ||
| view: 'team', | ||
| teamRankingData, | ||
| chartData, | ||
| timeRange | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Guard against empty teams to avoid NaN in JSON (division by zero)
When departmentAgents is empty, teamAvgScore becomes NaN (0/0), producing nulls in JSON for chart values. This degrades the UI.
Apply this diff to early-return a well-formed empty dataset:
- const teamAvgScore = teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;
-
- const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
- period: i + 1,
- responseTime: 3.2 + (Math.sin(i * 0.4) * 1.0),
- satisfaction: Math.max(75, teamAvgScore + (Math.cos(i * 0.3) * 8)),
- resolution: Math.max(70, teamAvgScore + (Math.sin(i * 0.5) * 6)),
- tickets: Math.max(2, Math.round(15 + (Math.sin(i * 0.6) * 5)))
- }));
-
- return NextResponse.json({
- success: true,
- data: {
- view: 'team',
- teamRankingData,
- chartData,
- timeRange
- }
- });
+ if (teamRankingData.length === 0) {
+ return NextResponse.json({
+ success: true,
+ data: {
+ view: 'team',
+ teamRankingData: [],
+ chartData: [],
+ timeRange
+ }
+ });
+ }
+
+ const teamAvgScore =
+ teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;
+
+ const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
+ period: i + 1,
+ responseTime: 3.2 + Math.sin(i * 0.4) * 1.0,
+ satisfaction: Math.max(75, teamAvgScore + Math.cos(i * 0.3) * 8),
+ resolution: Math.max(70, teamAvgScore + Math.sin(i * 0.5) * 6),
+ tickets: Math.max(2, Math.round(15 + Math.sin(i * 0.6) * 5)),
+ }));
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ view: 'team',
+ teamRankingData,
+ chartData,
+ timeRange,
+ },
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const teamAvgScore = teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length; | |
| const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({ | |
| period: i + 1, | |
| responseTime: 3.2 + (Math.sin(i * 0.4) * 1.0), | |
| satisfaction: Math.max(75, teamAvgScore + (Math.cos(i * 0.3) * 8)), | |
| resolution: Math.max(70, teamAvgScore + (Math.sin(i * 0.5) * 6)), | |
| tickets: Math.max(2, Math.round(15 + (Math.sin(i * 0.6) * 5))) | |
| })); | |
| return NextResponse.json({ | |
| success: true, | |
| data: { | |
| view: 'team', | |
| teamRankingData, | |
| chartData, | |
| timeRange | |
| } | |
| }); | |
| } | |
| if (teamRankingData.length === 0) { | |
| return NextResponse.json({ | |
| success: true, | |
| data: { | |
| view: 'team', | |
| teamRankingData: [], | |
| chartData: [], | |
| timeRange | |
| } | |
| }); | |
| } | |
| const teamAvgScore = | |
| teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length; | |
| const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({ | |
| period: i + 1, | |
| responseTime: 3.2 + Math.sin(i * 0.4) * 1.0, | |
| satisfaction: Math.max(75, teamAvgScore + Math.cos(i * 0.3) * 8), | |
| resolution: Math.max(70, teamAvgScore + Math.sin(i * 0.5) * 6), | |
| tickets: Math.max(2, Math.round(15 + Math.sin(i * 0.6) * 5)), | |
| })); | |
| return NextResponse.json({ | |
| success: true, | |
| data: { | |
| view: 'team', | |
| teamRankingData, | |
| chartData, | |
| timeRange, | |
| }, | |
| }); | |
| } |
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/performance/route.ts around lines 182 to 201,
guard against empty teams to avoid dividing by zero and producing NaN in the
JSON: if teamRankingData is empty, early-return a well-formed response (success:
true) with view: 'team', teamRankingData: [], chartData: [] (or an appropriate
empty chart shape), and timeRange so the UI receives consistent types;
alternatively compute teamAvgScore with a conditional (teamRankingData.length ?
sum/length : 0) before building chartData to prevent NaN.
| generatedDate: appointment.createdAt, | ||
| size: `${calculatedSize} MB`, | ||
| status: 'completed' |
There was a problem hiding this comment.
Mismatch with frontend: generatedDate should be generatedAt
ReportGenerator.tsx expects generatedAt; returning generatedDate breaks date rendering.
- generatedDate: appointment.createdAt,
+ generatedAt: appointment.createdAt,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| generatedDate: appointment.createdAt, | |
| size: `${calculatedSize} MB`, | |
| status: 'completed' | |
| generatedAt: appointment.createdAt, | |
| size: `${calculatedSize} MB`, | |
| status: 'completed' |
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/reports/route.ts around lines 44 to 46 the
response object uses generatedDate but the frontend expects generatedAt; rename
the property from generatedDate to generatedAt in the returned report object
(and update any related types/interfaces if present) so the frontend can
correctly read and render the date.
| if (action === 'generateReport') { | ||
| // Get agent's data for report generation | ||
| const agentAppointments = await Appointment.find({ | ||
| assignedAgent: agentId | ||
| }); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Filter by date range in the DB instead of in-memory
You fetch all agent appointments and then filter in-memory. Push the date filter into the DB query to reduce memory usage and speed up response time.
- // Get agent's data for report generation
- const agentAppointments = await Appointment.find({
- assignedAgent: agentId
- });
+ // (Moved: fetch periodAppointments after computing date range)- // Filter appointments by date range
- const periodAppointments = agentAppointments.filter(apt =>
- apt.createdAt >= startDate && apt.createdAt <= now
- );
+ // Fetch appointments for the computed date range directly from DB
+ const periodAppointments = await Appointment.find({
+ assignedAgent: agentId,
+ createdAt: { $gte: startDate, $lte: endDate }
+ }).select('createdAt status serviceType');Also applies to: 148-151
| return NextResponse.json({ | ||
| success: true, | ||
| data: { | ||
| reportId, | ||
| reportData, | ||
| downloadUrl: `/api/agent/analytics/reports/download/${reportId}`, | ||
| message: 'Report generated successfully' | ||
| } | ||
| }); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Follow-up: downloadUrl points to a non-existent route
The response advertises /api/agent/analytics/reports/download/:id but there’s no handler in this PR. Either add a stub route or remove the URL until implemented.
Would you like me to add a minimal download route that returns a mocked file stream or a 501 Not Implemented placeholder?
Run this to check for a download handler:
🏁 Script executed:
#!/bin/bash
rg -n -C2 -g '!**/node_modules/**' -P 'analytics/reports/download'Length of output: 490
Implement the missing download route or remove its reference
The downloadUrl in your JSON response points to /api/agent/analytics/reports/download/:id, but there’s no handler for this endpoint in the PR. This will lead to 404s at runtime.
Please choose one of the following fixes:
- Add a stub download route
Createsrc/app/api/agent/analytics/reports/download/route.tswith at least a placeholder implementation, e.g.:import { NextResponse } from 'next/server'; export async function GET(request: Request, { params }: { params: { id: string } }) { // TODO: replace with real file streaming logic return NextResponse.json( { message: `Download for report ${params.id} is not implemented yet.` }, { status: 501 } ); }
- Remove the
downloadUrlfield
If you’re not ready to support downloads, omitdownloadUrl(and related front-end logic) until the endpoint is implemented.
Please address before merging.
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/reports/route.ts around lines 180-188, the JSON
response includes downloadUrl pointing to
/api/agent/analytics/reports/download/:id but there is no handler for that route
causing 404s; either add a new route handler file at
src/app/api/agent/analytics/reports/download/route.ts that exports an async GET
accepting params.id and returns a placeholder 501 JSON response (or actual
streaming logic later), or remove the downloadUrl field from the response (and
any frontend usage) until the endpoint is implemented.
| const prevPeriodAppointments = await Appointment.find({ | ||
| assignedAgent: agentId, | ||
| createdAt: { $gte: prevStartDate, $lte: prevEndDate } | ||
| }); |
There was a problem hiding this comment.
Fix previous-period overlap (double-counting at the boundary)
Using <= prevEndDate where prevEndDate equals startDate includes appointments exactly at startDate in both current and previous windows. Use $lt: startDate for the previous period upper bound.
Apply this diff:
- const prevPeriodAppointments = await Appointment.find({
- assignedAgent: agentId,
- createdAt: { $gte: prevStartDate, $lte: prevEndDate }
- });
+ const prevPeriodAppointments = await Appointment.find({
+ assignedAgent: agentId,
+ createdAt: { $gte: prevStartDate, $lt: startDate }
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const prevPeriodAppointments = await Appointment.find({ | |
| assignedAgent: agentId, | |
| createdAt: { $gte: prevStartDate, $lte: prevEndDate } | |
| }); | |
| const prevPeriodAppointments = await Appointment.find({ | |
| assignedAgent: agentId, | |
| createdAt: { $gte: prevStartDate, $lt: startDate } | |
| }); |
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/route.ts around lines 70 to 73, the
previous-period query uses createdAt: { $gte: prevStartDate, $lte: prevEndDate }
which double-counts appointments exactly at the boundary; change the upper bound
to be exclusive by using $lt: startDate (or $lt: prevEndDate if prevEndDate
equals startDate) so the previous period becomes createdAt: { $gte:
prevStartDate, $lt: startDate } to avoid overlap with the current period.
| const periodAppointments = appointments.filter(apt => | ||
| apt.createdAt >= periodStart && apt.createdAt < periodEnd | ||
| ); |
There was a problem hiding this comment.
Edge case: last bin excludes appointments at exactly "now"
Appointments with createdAt equal to the computed last bin end (which equals "now") are included in the DB query (<= now) but excluded when binning (< periodEnd). Fix by including <= for the last bin only.
Apply this diff within the binning loop:
- const periodAppointments = appointments.filter(apt =>
- apt.createdAt >= periodStart && apt.createdAt < periodEnd
- );
+ const isLastBin = i === dataPoints - 1;
+ const periodAppointments = appointments.filter(apt =>
+ apt.createdAt >= periodStart && (isLastBin ? apt.createdAt <= periodEnd : apt.createdAt < periodEnd)
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const periodAppointments = appointments.filter(apt => | |
| apt.createdAt >= periodStart && apt.createdAt < periodEnd | |
| ); | |
| const isLastBin = i === dataPoints - 1; | |
| const periodAppointments = appointments.filter(apt => | |
| apt.createdAt >= periodStart && | |
| (isLastBin ? apt.createdAt <= periodEnd : apt.createdAt < periodEnd) | |
| ); |
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/trends/route.ts around lines 58 to 60, the
binning filter currently excludes appointments whose createdAt equals the final
periodEnd (now) even though the DB query includes <= now; update the filter to
include createdAt <= periodEnd for the last bin only (e.g., detect the last
iteration by index or compare periodEnd to now) while keeping the existing <
periodEnd behavior for earlier bins so appointments at exactly "now" are counted
in the final bin.
| value: calculateChange(pendingAppointments, lastWeekPending), | ||
| isPositive: pendingAppointments >= lastWeekPending | ||
| } |
There was a problem hiding this comment.
Trend polarity for pending appointments is inverted
An increase in pending items is typically negative. Invert isPositive to reflect improvements when pending decreases.
Apply this diff:
- value: calculateChange(pendingAppointments, lastWeekPending),
- isPositive: pendingAppointments >= lastWeekPending
+ value: calculateChange(pendingAppointments, lastWeekPending),
+ isPositive: pendingAppointments <= lastWeekPending📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| value: calculateChange(pendingAppointments, lastWeekPending), | |
| isPositive: pendingAppointments >= lastWeekPending | |
| } | |
| value: calculateChange(pendingAppointments, lastWeekPending), | |
| isPositive: pendingAppointments <= lastWeekPending |
🤖 Prompt for AI Agents
In src/app/api/agent/dashboard/route.ts around lines 53 to 55, the trend
polarity for pending appointments is inverted: currently isPositive is true when
pendingAppointments >= lastWeekPending, but a decrease in pending appointments
is an improvement. Change the isPositive expression to reflect improvement on
decrease (e.g., set isPositive to pendingAppointments <= lastWeekPending or
invert the boolean) so that a lower current pending count yields a positive
trend.
| export async function GET() { | ||
| try { | ||
| await connectDB(); | ||
|
|
There was a problem hiding this comment.
Guard the debug route behind an environment check
This endpoint exposes a list of admins (emails, roles). It should be disabled in non-development environments.
Apply this diff:
export async function GET() {
try {
+ if (process.env.NODE_ENV !== "development") {
+ return NextResponse.json(
+ { success: false, message: "check-admins is disabled outside development" },
+ { status: 403 }
+ );
+ }
await connectDB();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function GET() { | |
| try { | |
| await connectDB(); | |
| export async function GET() { | |
| try { | |
| if (process.env.NODE_ENV !== "development") { | |
| return NextResponse.json( | |
| { success: false, message: "check-admins is disabled outside development" }, | |
| { status: 403 } | |
| ); | |
| } | |
| await connectDB(); |
🤖 Prompt for AI Agents
In src/app/api/debug/check-admins/route.ts around lines 5 to 8, the debug GET
route currently exposes admin emails/roles in all environments; add an early
environment guard before calling connectDB to disable this endpoint unless
running in development: check process.env.NODE_ENV === 'development' (or a
dedicated env var like DEBUG_ADMIN_ROUTE === 'true'), and if the check fails
immediately return a safe status (404 or 403) without connecting to the DB or
returning data; keep the guard first line in the handler so no sensitive info is
reachable in production.
| {loading ? ( | ||
| <div className="flex items-center justify-center h-64"> | ||
| <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#FFC72C]"></div> | ||
| </div> | ||
| ) : recentReports.length === 0 ? ( | ||
| <div className="text-center py-12"> | ||
| <div className="p-4 bg-muted/20 rounded-full w-16 h-16 mx-auto mb-4 flex items-center justify-center"> | ||
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | ||
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> | ||
| <polyline points="14 2 14 8 20 8"/> | ||
| </svg> | ||
| </div> | ||
| <h3 className="text-lg font-semibold text-foreground mb-2">No Reports Yet</h3> | ||
| <p className="text-muted-foreground">Generate your first report to see it here.</p> | ||
| </div> | ||
| ) : ( |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
History view: frontend expects generatedAt; ensure backend matches or add fallback
ReportGenerator renders report.generatedAt. With the backend fixed to return generatedAt (see API comment), this will work. If not, add a minimal fallback to support generatedDate to avoid runtime issues.
Run this to verify both sides use generatedAt consistently:
Also applies to: 848-895
🏁 Script executed:
#!/bin/bash
rg -n -C2 -g '!**/node_modules/**' -P '\bgeneratedAt\b|\bgeneratedDate\b'Length of output: 4908
I’d like to inspect the two places in the API to confirm how generatedDate vs. generatedAt are returned.
🏁 Script executed:
#!/bin/bash
# Show context around the use of generatedDate
rg -nC3 "generatedDate" src/app/api/agent/analytics/reports/route.ts
# Show context around the use of generatedAt
rg -nC3 "generatedAt" src/app/api/agent/analytics/reports/route.tsLength of output: 657
Unify report timestamp field across API and frontend
The API handler in src/app/api/agent/analytics/reports/route.ts returns generatedDate for the initial history payload (line 44) but uses generatedAt elsewhere (line 169), whereas ReportGenerator.tsx only reads report.generatedAt. This mismatch will cause undefined timestamps and potential runtime errors.
Please address one of the following:
- Backend fix (preferred):
- In
src/app/api/agent/analytics/reports/route.tsat line 44, change- generatedDate: appointment.createdAt, + generatedAt: appointment.createdAt,
- In
- Frontend fallback:
- In
src/components/agent/analytics/ReportGenerator.tsx, replacewith<span>{formatDate(report.generatedAt)}</span>
<span>{formatDate(report.generatedAt ?? report.generatedDate)}</span>
- In
🤖 Prompt for AI Agents
In src/components/agent/analytics/ReportGenerator.tsx around lines 831-846 the
UI reads only report.generatedAt which can be undefined because the backend
sometimes returns generatedDate; update the rendering to fall back to
generatedDate when generatedAt is missing (use a nullish-coalescing or
conditional fallback so formatDate is passed report.generatedAt if present
otherwise report.generatedDate), and adjust the report type/props or add a safe
optional check so TypeScript and runtime won't error when one of the fields is
undefined.
Pull Request
Summary
Area of change
Type of change
If Other, describe:
Screenshots or recordings (UI changes)
How to test
Acceptance criteria checklist
Accessibility checklist (if UI)
Security & privacy
Breaking changes
If breaking, describe migration steps and rollback plan:
Deployment notes
Additional notes
Summary by CodeRabbit